package query

import (
	"context"
	"time"

	"code.byted.org/bytertc/whiteboard_server/biz/dal/spec"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/biz_error"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action/lock"
	"code.byted.org/gopkg/logs/v2/log"
	"github.com/pkg/errors"
	"google.golang.org/protobuf/proto"

	v1 "code.byted.org/bytertc/whiteboard_server/biz/api/pb/v1"
	actBase "code.byted.org/bytertc/whiteboard_server/biz/service/action/base"
	"code.byted.org/bytertc/whiteboard_server/biz/service/command"
	"code.byted.org/bytertc/whiteboard_server/biz/service/command/base"
)

func NewHeartbeatCommand(ctx context.Context, cmd *base.Command, boardId, version int32, pageId string, myEditingObjIds []string) *heartbeatCommand {
	return &heartbeatCommand{
		Command:         *cmd,
		boardId:         boardId,
		pageId:          pageId,
		myEditingObjIds: myEditingObjIds,
	}
}

type heartbeatCommand struct {
	base.Command
	boardId         int32
	pageId          string
	myEditingObjIds []string
}

func (h *heartbeatCommand) GetBoardId() int32 {
	return h.boardId
}
func (h *heartbeatCommand) GetPageId() string {
	return h.pageId
}

func (h *heartbeatCommand) Execute(ctx context.Context) []command.Result {
	h.lockObjIdsKeepLive(ctx, h.myEditingObjIds)
	canBroadcast, _ := h.getBroadcastStatus(ctx, h)
	if !canBroadcast {
		return nil
	}
	err := h.updateUserTimeStamp(ctx, h)
	if err != nil {
		log.V1.CtxError(ctx, "updateUserTimeStamp fail roomId:%s,app:%s,operator:%s,err:%+v", h.RoomId, h.AppId, h.Operator, err)
	}
	data, err := h.gatherData(ctx)
	res := &base.Result{
		Msg: &v1.HeartBeatResponse{
			BoardId:   h.boardId,
			PageId:    h.pageId,
			HeartBeat: data,
		},
		IsBroadcast: true,
		CommandType: v1.Command_kHeartBeat,
		Error:       err,
	}
	return []command.Result{res}
}

func (h *heartbeatCommand) gatherData(ctx context.Context) (*v1.HeartBeatData, error) {
	//优先获取actionCount
	actionCount, err := h.Repo.GetPageActionCount(ctx, h)
	if err != nil {
		return nil, err
	}
	userLocked, err := h.Repo.GetLockedObjects(ctx, h)
	if err != nil {
		return nil, err
	}
	controlCount, err := h.Repo.GetBoardControlCount(ctx, h)
	if err != nil {
		return nil, err
	}
	boardInfos, err := h.Repo.GetBoards(ctx, h, []int32{h.boardId})
	if err != nil {
		return nil, err
	}
	if len(boardInfos) == 0 {
		return nil, biz_error.InvalidParam.Wrap(errors.Errorf("invalid boardId:%d", h.boardId))
	}
	cursorInfo, err := h.Repo.GetTopNCursorState(ctx, h)
	if err != nil {
		return nil, err
	}
	zoomInfo, err := h.Repo.GetZoomInfo(ctx, h)
	if err != nil {
		return nil, err
	}

	pptInfo := &v1.HeartBeatPptInfo{
		CurrentStep: boardInfos[0].CurrentStep,
		Medias:      boardInfos[0].PptCurrentMedias,
		Triggers:    boardInfos[0].PptCurrentTriggers,
	}
	if boardInfos[0].PptInfo != nil {
		pptInfo.FileId = boardInfos[0].PptInfo.FileId
	}
	data := &v1.HeartBeatData{
		ActionCount:        actionCount,
		ControlCount:       controlCount,
		UserLockObjIds:     userLocked,
		BoardCurrentPageId: &boardInfos[0].CurrentPageId,
		BoardControlCount:  *boardInfos[0].BoardControlCount,
		UserCursorInfo:     cursorInfo,
		ZoomInfo:           zoomInfo,
		PptInfo:            pptInfo,
	}

	return data, nil
}
func (h *heartbeatCommand) lockObjIdsKeepLive(ctx context.Context, objIds []string) error {
	lockDetail := &v1.LockDetail{
		ObjIds:      objIds,
		LockObjType: v1.LockObjType_kElement,
	}
	data, _ := proto.Marshal(lockDetail)
	info := action.Info{
		Operator: h.Operator,
		AppId:    h.AppId,
		RoomId:   h.RoomId,
		BoardId:  h.boardId,
		PageId:   h.pageId,
	}
	act := &v1.Action{
		Data: data,
		Type: v1.ActionType_kLock,
	}
	action, err := lock.NewAction(ctx, actBase.Action{Info: info, Repo: h.Repo}, act)
	if err != nil {
		log.V1.CtxError(ctx, "lock.NewAction %+v,err:%+v", objIds, err)
		return err
	}
	err = action.Execute(ctx)
	if err != nil {
		log.V1.CtxError(ctx, "lockAction.Execute %+v,err:%+v", objIds, err)
		return err
	}
	return nil
}
func (h *heartbeatCommand) getBroadcastStatus(ctx context.Context, unique spec.RoomUnique) (bool, error) {
	return h.Repo.SetHeartbeatStamp(ctx, unique)
}
func (h *heartbeatCommand) updateUserTimeStamp(ctx context.Context, unique spec.OperatorUnique) error {
	timestamp := time.Now().UnixNano() / 1e6
	err := h.Repo.SetFirstHbTimeStamp(ctx, unique, timestamp)
	if err != nil {
		log.V1.CtxError(ctx, "SetFirstHbTimeStamp fail roomId:%s,operator:%s,err:%+v", unique.GetRoomId(), unique.GetOperator(), err)
	}
	return h.Repo.UpdateHeartbeatTimeStamp(ctx, unique, timestamp)
}
